package top.malaoshi.webmvc;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RestController;
import top.malaoshi.util.ArrayUtil;
import top.malaoshi.util.Tuple2;
import top.malaoshi.webmvc.exception.BeanCreationException;
import top.malaoshi.webmvc.exception.SameNameBeanException;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.*;

public class AnnotationConfigApplicationContext implements ApplicationContext {
    /**
     * 扫描的类路径集合
     */
//    List<String> classPaths = new ArrayList<>();
    /**
     * IOC容器
     */
    Map<String, Object> beans = new HashMap<>();



    public void execute(String packagePath) throws Exception {
        // 1 扫描包路径、并实例化
        scanPackageAndInstance(packagePath);

        // 2 处理依赖的注入
        resolveDependencies();

        // 3 扫描 @Bean注解
        scanBeanAnnotation();
        // 4 再次处理依赖的注入
        resolveDependencies();
        // 5 检查成员变量是否为null
        checkFieldIsNull();
        // 6 执行 @PostConstruct 注解修饰的方法
        executePostConstruct();
        // 执行 Runner 接口的类
        executeRunner();
    }

    public Map<String, Object> getBeans() {
        return beans;
    }

    /**
     * 扫描需要加载的类路径
     *
     * @param basePackage
     */
    private void scanPackageAndInstance(String basePackage) throws FileNotFoundException, ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException, NoSuchMethodException {
        //获取要加载的包路径
        URL resource = this.getClass().getClassLoader().getResource("/" + basePackage.replaceAll("\\.", "/"));
        if(resource == null){
            throw new FileNotFoundException("路径 [ "+basePackage + " ] 不存在！");
        }
        String basePath = resource.getFile();
        File baseDir = new File(basePath);
        //递归遍历目录下的class文件，收集要加载的类路径
        File[] files = baseDir.listFiles();
        for (File file : files) {
            if (file.isDirectory()) {
                scanPackageAndInstance(basePackage + "." + file.getName());
            } else {
                if (file.getName().endsWith(".class")) {
                    String classPaths = basePackage + "." + file.getName().replaceAll(".class", "");
                    newInstance(classPaths);
                }
            }
        }
    }
    private void newInstance(String classPath) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
        try {
            Class<?> clazz = Class.forName(classPath);
            String name = null;
            if (clazz.isAnnotationPresent(Controller.class)) {
                Controller controller = clazz.getAnnotation(Controller.class);
                name = controller.value();
            }else if(clazz.isAnnotationPresent(RestController.class)){
                RestController restController = clazz.getAnnotation(RestController.class);
                name = restController.value();
            }else if(clazz.isAnnotationPresent(Component.class)){
                Component annotation = clazz.getAnnotation(Component.class);
                name = annotation.value();
            }else if(clazz.isAnnotationPresent(Service.class)){
                Service annotation = clazz.getAnnotation(Service.class);
                name = annotation.value();
            }else if(clazz.isAnnotationPresent(Configuration.class)){
                Configuration annotation = clazz.getAnnotation(Configuration.class);
                name = annotation.value();
            }else{
                return;
            }
            // 如果没有配置注解的 value 属性，就将类名作为实例名，首字幕小写
            if("".equals(name)){
                name = lowerFirstChar(clazz.getSimpleName());
            }
            if(beans.containsKey(name)){
                throw new SameNameBeanException(name,clazz);
            }
            // 不能使用 clazz.newInstance() 创建对象，遇到 private 私有构造方法会报错无权限
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            // 设置访问权限，无视private关键字
            constructor.setAccessible(true);
            Object bean = constructor.newInstance();
            beans.put(name, bean);
        } catch (ClassNotFoundException e) {
            throw e;
        } catch (IllegalAccessException e) {
            throw e;
        } catch (InstantiationException e) {
            throw e;
        } catch (NoSuchMethodException e) {
            throw e;
        } catch (InvocationTargetException e) {
            throw e;
        }

    }

    /**
     * 根据类型查找bean，该类型可能是接口、父类，该 bean 是接口的实现类、子类即可
     * @param clazz
     * @return Tuple2，第一个元素是名字数组，第二个元素是 bean数组
     */
    public Tuple2<String[],Object[]> getBeanByType(Class clazz){
        List beanList = new ArrayList();
        List<String> beanNames = new ArrayList();
        beans.forEach((beanName, instance) -> {
//            if(instance.getClass() == clazz){
            if(clazz.isAssignableFrom(instance.getClass())){
                beanList.add(instance);
                beanNames.add(beanName);
            }
        });
        String[] beanNameArr = new String[beanNames.size()];
        return Tuple2.create(beanNames.toArray(beanNameArr),beanList.toArray());
    }
    /**
     * 处理自动注入
     */
    private void resolveDependencies() throws IllegalAccessException {
        //遍历IOC容器，解决bean依赖问题，处理Autowired自动注入
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        for(Map.Entry<String, Object> item : entries){
            String beanName = item.getKey();
            Object instance = item.getValue();

            Class<?> clazz = instance.getClass();
            if (isHandleClassAnnotation(clazz)) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    // 处理 Autowired 注解
                    if (field.isAnnotationPresent(Autowired.class)  ) {
                        Autowired anno = field.getAnnotation(Autowired.class);
                        // 获取该bean的成员变量的值
                        field.setAccessible(true);
                        Object fieldValue = field.get(instance);
                        // 如果该 bean的成员变量的值 不为null，说明已经注入过，那么就不继续执行
                        if(fieldValue!=null){
                            continue;
                        }
                        injectByFieldClass(beanName,instance,field);
                    }else if( field.isAnnotationPresent(Resource.class) ){
                        // 获取该bean的成员变量的值
                        field.setAccessible(true);
                        Object fieldValue = field.get(instance);
                        // 如果该 bean的成员变量的值 不为null，说明已经注入过，那么就不继续执行
                        if(fieldValue!=null){
                            continue;
                        }
                        // 处理 Resource 注解
                        Resource anno = field.getAnnotation(Resource.class);
                        String name = anno.name().trim();
                        Class<?> type = anno.type();
                        // @Resource 注解的type默认是 Object.class 类型，所以为了正常运行，这里赋值为null
                        if(type == Object.class){
                            type = null;
                        }
                        // 如果注解中name、type属性不为空
                        if(name!=null && !"".equals(name) && type!=null){
                            Object o = beans.get(name);
                            // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
                            if(o == null){

                            }else if(o.getClass() == type){
                                field.setAccessible(true);
                                try {
                                    field.set(instance, o);
                                } catch (IllegalAccessException e) {
                                    throw e;
                                }
                            }else{
                                throw new BeanCreationException(beanName,field.getDeclaringClass().getName(),type.getName());
                            }

                        }else if(name!=null && !"".equals(name)){ // 如果注解中name属性不为空

                            Object o = beans.get(name);
                            // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
                            if(o!=null) {
                                try {
                                    field.set(instance, o );
                                } catch (IllegalAccessException e) {
                                    throw e;
                                }
                            }
                        }else if(type!=null){ // 如果注解中 type 不为空
                            injectByFieldClass(beanName,instance,field);
                        }else { // 如果注解中 name、type属性都为空
                            String fieldName = field.getName();
                            Object bean = beans.get(fieldName);
                            if(bean!=null) {
                                try {
                                    field.set(instance, bean);
                                } catch (IllegalAccessException e) {
                                    throw e;
                                }
                            }else{

                                injectByFieldClass(beanName,instance,field);

                            }
                        }
                    }
                }
            }
        }
    }


    /**
     * 根据成员变量的类型进行注入
     * @param beanName
     * @param bean
     * @param field
     */
    private void injectByFieldClass(String beanName,Object bean,Field field) throws IllegalAccessException {

        Class<?> fieldType = field.getType();
        Tuple2<String[],Object[]> tuple2 = getBeanByType(fieldType);
        Object[] beans = tuple2.getSecond();
        // 如果从bean中没有获取到对象，则不处理，后面还处理@Bean注解，之后再次处理依赖
        if(beans == null || beans.length == 0){
//            throw new BeanCreationException(beanName,fieldType.getName());
        }else if(beans.length>1){
            throw new BeanCreationException(beanName,fieldType.getName(),beans.length,tuple2.getFirst());
        }else{
            field.set(bean, beans[0]);
        }
    }

    /**
     * 将字符串的首字母转换为小写返回
     *
     * @param origin
     * @return
     */
    private String lowerFirstChar(String origin) {
        if (origin == null || origin.trim().length() == 0)
            return origin;
        char[] chars = origin.toCharArray();
        if (chars[0] >= 'A' && chars[0] <= 'Z') {
            chars[0] += 32;
        }
        return new String(chars);
    }

    /**
     * 执行 实现 ApplicationRunner 和 CommandLineRunner接口的类
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void executeRunner() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        for(Map.Entry<String,Object> item : entries){

            Class beanClass = item.getValue().getClass();
            if(ApplicationRunner.class.isAssignableFrom(beanClass) ||
                    CommandLineRunner.class.isAssignableFrom(beanClass)){
                Method run = beanClass.getMethod("run");
                run.invoke(item.getValue());
            }
        }
    }

    /**
     * 执行 @PostConstruct 注解修饰的方法
     */
    private void executePostConstruct() throws InvocationTargetException, IllegalAccessException {
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        for(Map.Entry<String,Object> item : entries){

            Object bean = item.getValue();
            Method[] methods = bean.getClass().getDeclaredMethods();
            for(Method method : methods){

                if(method.isAnnotationPresent(PostConstruct.class)){
                    method.invoke(bean);
                }

            }
        }
    }

    /**
     * 扫描 @Bean注解
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    private void scanBeanAnnotation() throws InvocationTargetException, IllegalAccessException {
        // 临时的容器，调用 @Bean 修饰的方法，将返回值放入到 该临时容器中，如果放入到 beans 容器中，会报错
        Map<String,Object> tempBean = new HashMap<>();
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        for(Map.Entry<String,Object> item : entries){

            Object bean = item.getValue();
            Class<?> beanClass = bean.getClass();
            Method[] methods = beanClass.getDeclaredMethods();
            for(Method method : methods){

                if(method.isAnnotationPresent(Bean.class)){
                    Bean ann = method.getAnnotation(Bean.class);
                    String[] value = ann.value();
                    String[] name = ann.name();

                    String[] valuesAndNames = ArrayUtil.concatAll(value, name);
                    Object res = method.invoke(bean);
                    // 如果没有value、name配置
                    if(valuesAndNames == null || valuesAndNames.length == 0){
                        String beanName = lowerFirstChar(method.getName());
                        if(beans.containsKey(beanName)){
                            throw new SameNameBeanException(beanName,beanClass,method);
                        }
                        if(tempBean.containsKey(beanName)){
                            throw new SameNameBeanException(beanName,beanClass,method);
                        }
                        tempBean.put(beanName, res);
                    }else{
                        for(String valueAndName : valuesAndNames){
                            if(beans.containsKey(valueAndName)){
                                throw new SameNameBeanException(valueAndName,beanClass,method);
                            }
                            if(tempBean.containsKey(valueAndName)){
                                throw new SameNameBeanException(valueAndName,beanClass,method);
                            }
                            tempBean.put(valueAndName, res);
                        }
                    }
                }

            }
        }

        // 最后将 tempBean 中的数据，放入到 bean中
        beans.putAll(tempBean);
    }

    /**
     * 判断是否处理 Controller、RestController、Component、Service、Configuration 注解
     * @param clazz
     * @return
     */
    private boolean isHandleClassAnnotation(Class clazz){
        return clazz.isAnnotationPresent(Controller.class) ||
                clazz.isAnnotationPresent(RestController.class) ||
                clazz.isAnnotationPresent(Component.class) ||
                clazz.isAnnotationPresent(Service.class) ||
                clazz.isAnnotationPresent(Configuration.class);
    }
    /**
     * 如果类上有 Controller、RestController、Component、Service、Configuration 注解 修饰，
     * 那么就获取其所有成员变量，如果成员变量由 Autowired、Resource 修饰，
     * 就判断这些成员变量的值是否为null，为null就抛异常
     */
    private void checkFieldIsNull() throws IllegalAccessException {
        //遍历IOC容器，解决bean依赖问题，处理Autowired自动注入
        Set<Map.Entry<String, Object>> entries = beans.entrySet();
        for(Map.Entry<String, Object> item : entries) {
            String beanName = item.getKey();
            Object instance = item.getValue();

            Class<?> clazz = instance.getClass();
            if (isHandleClassAnnotation(clazz)) {
                Field[] fields = clazz.getDeclaredFields();
                for (Field field : fields) {
                    // 如果该成员变量有 Autowired 或 Resource 注解 修饰，就判断是否为 null，为null就抛异常
                    if (field.isAnnotationPresent(Autowired.class) ||
                            field.isAnnotationPresent(Resource.class)) {
                        field.setAccessible(true);
                        Object fieldValue = field.get(instance);
                        if(fieldValue == null){
                            throw new BeanCreationException(beanName,field.getType().getName());
                        }

                    }
                }
            }
        }
    }

}
